﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Collections.ObjectModel;
using System.Collections;
using System.Threading;

using UT3Bots.Communications;
using UT3Bots.UTItems;
using UT3Bots.Visualizer.Visualizer.UTItems;


namespace UT3Bots.Visualizer.Controls
{
	public partial class UTUIMap : UserControl
	{
		private static readonly TimeSpan CHECK_INTERVAL = new TimeSpan(0, 0, 10);
		private static UTUIMap Singleton;

        private const int UT_PORT = 4531;
		private string _utServer = "174.133.254.34";

        private double _xScale;
        private double _yScale;
		private int _imgCenterX;
		private int _imgCenterY;
        private Canvas _pageRoot;

		private UT3Connection _connection;
		private Dictionary<UTIdentifier, UTNavPoint> _utNavPoints = new Dictionary<UTIdentifier, UTNavPoint>();
		private Dictionary<UTIdentifier, UTItemPoint> _utItemPoints = new Dictionary<UTIdentifier, UTItemPoint>();
		private Dictionary<UTIdentifier, UTBotOppState> _utBots = new Dictionary<UTIdentifier, UTBotOppState>();
		private Dictionary<UTIdentifier, UTUIScore> _scores = new Dictionary<UTIdentifier, UTUIScore>();
		private ObservableCollection<UTUIScore> _observableScores = new ObservableCollection<UTUIScore>();

		private bool _isMapInitialized = false;
		private DateTime _lastCheckTime;

		private bool _isRunning = false;
		private Thread _mainThread;
		private Queue<Message> _queue = new Queue<Message>();
		private int _stateSections = 0;

		#region Properties
		public static UTUIMap CurrentMap
		{
			get { return Singleton; }
		}
		public double XScale
		{
			get { return _xScale; }
		}

		public double YScale
		{
			get { return _yScale; }
		}

		public int XCenter
		{
			get { return _imgCenterX; }
		}

		public int YCenter
		{
			get { return _imgCenterY; }
		}

		public string MapName
		{
			get { return txtMapName.Text; }
		}
		#endregion


		public UTUIMap()
		{
			// Required to initialize variables
			InitializeComponent();
		}

		public void Initialize(Canvas pageRoot, string ipAddress)
        {

            // Required to initialize variables
            InitializeComponent();

            UTUIMap.Singleton = this;

            _pageRoot = pageRoot;
			_utServer = ipAddress;

            Canvas.SetLeft(this, 0);
            Canvas.SetTop(this, 0);

			//Start Collecting Data
			_connection = new UT3Connection(_utServer, UT_PORT);
			_connection.OnDataReceived += new EventHandler<TcpDataEventArgs>(connection_OnDataReceived);
			_connection.OnErrorOccurred += new EventHandler<TcpErrorEventArgs>(connection_OnErrorOccurred);

			//Start the running thread
			this._isRunning = true;
			this._mainThread = new Thread(new ThreadStart(Run));
			this._mainThread.IsBackground = false;
			this._mainThread.Start();
		}

		private void SetupMap(string mapName)
		{

			switch (mapName)
			{
				case "20":
					mapName = "Stalwart";
					this._xScale = 0.221;
					this._yScale = 0.196;
					this._imgCenterX = 482;
					this._imgCenterY = -350;
					break;

				case "BioHazard":
				default:
					mapName = "BioHazard";
					this._xScale = 0.23;
					this._yScale = 0.23;
					this._imgCenterX = 344;
					this._imgCenterY = 291;
					break;
			}

            
            txtMapName.Text = "UT3BotDeathmatch On " + mapName;

            BitmapImage bmi = new BitmapImage(new Uri("/UTMaps/DM-" + mapName + ".jpg", UriKind.Relative));
            imgMapImage.Source = bmi;
			imgMapImage.SizeChanged += new SizeChangedEventHandler(imgMapImage_SizeChanged);
        }

		private void Run()
		{
			while (this._isRunning)
			{
				if (this._queue.Count > 0)
				{
					lock (((ICollection)this._queue).SyncRoot)
					{
						Message msg = this._queue.Dequeue();
						this.Dispatcher.BeginInvoke(delegate()
						{
							ProcessEvent(msg);
							if (msg.Info == InfoMessage.END)
							{
								_stateSections--;
							}
						});
					}
				}

			}
		}



		private void connection_OnErrorOccurred(object sender, TcpErrorEventArgs e)
        {
			Dispatcher.BeginInvoke(delegate()
			{
				gridConnectionError.Visibility = Visibility.Visible;
			});
        }

		private void connection_OnDataReceived(object sender, TcpDataEventArgs e)
        {
            List<Message> messages = Message.FromVisualizerData(System.Text.UTF8Encoding.UTF8.GetString(e.Data, 0, e.Data.Length));

			lock (((ICollection)this._queue).SyncRoot)
			{
				foreach (Message msg in messages)
				{
					if (_stateSections < 10 || msg.IsEvent)
					{
						if (msg.Info == InfoMessage.END)
						{
							_stateSections++;
						}
						this._queue.Enqueue(msg);
					}
				}
			}
            
        }

        private void ProcessEvent(Message msg)
        {
            //Return if no valid message
            if (msg == null) return;

            //Do Command
            switch (msg.Event)
            {
				case EventMessage.STATE:
					//This is a state event so call that method
					ProcessStateEvent(msg);
                    break;

                case EventMessage.INFO:
					SetupMap(msg.Arguments[2]);
                    break;

                case EventMessage.MATCH_ENDED:
					_stateSections = 0;
                    break;

                case EventMessage.SPAWNED:

                    break;

                case EventMessage.DIED:

                    break;

                case EventMessage.KILLED:

                    break;

                case EventMessage.SEEN_PLAYER:

                    break;

                case EventMessage.BUMPED:

                    break;

                case EventMessage.HIT_WALL:

                    break;

                case EventMessage.HEARD_NOISE:

                    break;

                case EventMessage.DAMAGED:

                    break;

                case EventMessage.CHAT:
					//Somebody chatted
					PerformChatEvent(new UTIdentifier(msg.Arguments[0]),
							msg.Arguments[1],
							bool.Parse(msg.Arguments[2]),
							msg.Arguments[3]);
                    break;

                case EventMessage.FOUNDFALL:

                    break;

                case EventMessage.TAUNTED:

                    break;

                case EventMessage.WEAPON_CHANGED:

                    break;

                case EventMessage.GOT_PICKUP:

                    break;

                case EventMessage.PATH:

                    break;
            }
        }


		private void ProcessStateEvent(Message msg)
		{
			//Process the state message
				
			switch (msg.Info)
			{
				case InfoMessage.BEGIN:
					break;

				case InfoMessage.END:
					if (DateTime.Now > _lastCheckTime + CHECK_INTERVAL)
					{
						//this.Dispatcher.BeginInvoke(delegate()
						//{
							RemoveOldPlayers();
						//});
						_lastCheckTime = DateTime.Now;
					}
					break;

				case InfoMessage.NAV_INFO:
					UpdateNavPoint(msg);
					break;

				case InfoMessage.PICKUP_INFO:
					UpdateItemPoint(msg);
					break;

				case InfoMessage.PLAYER_INFO:
					UpdatePlayers(msg);
					break;

				case InfoMessage.SCORE_INFO:
					UpdateScores(msg);
					break;

			}
		}


		private void UpdateNavPoint(Message msg)
		{
			//Get the ID
			UTIdentifier id = new UTIdentifier(msg.Arguments[0]);

			//Check if we've already got it saved
			if (!this._utNavPoints.ContainsKey(id))
			{
				//Create a new one
				UTNavPoint nav = new UTNavPoint(id, UTVector.Parse(msg.Arguments[1]), bool.Parse(msg.Arguments[2]));
				this._utNavPoints.Add(id, nav);

				//this.Dispatcher.BeginInvoke(delegate()
				//{
					UTUINavPoint uiNav = new UTUINavPoint(nav);
					cvsNavPoints.Children.Add(uiNav);
				//});
			}
			else
			{
				//Just update it
				UTVector loc = UTVector.Parse(msg.Arguments[1]);
				//this.Dispatcher.BeginInvoke(delegate()
				//{
					this._utNavPoints[id].Location = loc;
				//});
			}
		}

		private void UpdateItemPoint(Message msg)
		{
			//Get the ID
			UTIdentifier id = new UTIdentifier(msg.Arguments[0]);

			//Check if we've already got it saved
			if (!this._utItemPoints.ContainsKey(id))
			{
				//Create a new one
				UTItemPoint item = new UTItemPoint(
										new UTIdentifier(msg.Arguments[0]),
										UTVector.Parse(msg.Arguments[1]),
										msg.Arguments[2],
										bool.Parse(msg.Arguments[3]),
										bool.Parse(msg.Arguments[4])
										); 
				this._utItemPoints.Add(id, item);

				//this.Dispatcher.BeginInvoke(delegate()
				//{
					UTUIItemPoint uiItem = new UTUIItemPoint(item);
					cvsItemPoints.Children.Add(uiItem);
				//});
			}
			else
			{
				//Just update it
				UTVector loc = UTVector.Parse(msg.Arguments[1]);
				bool ready = bool.Parse(msg.Arguments[4]);
				//this.Dispatcher.BeginInvoke(delegate()
				//{
					this._utItemPoints[id].Location = loc;
					this._utItemPoints[id].IsReadyToPickup = ready;
				//});
			}

		}

		private void UpdatePlayers(Message msg)
		{
			lock (this._utBots)
			{
				//Get the ID
				UTIdentifier id = new UTIdentifier(msg.Arguments[0]);

				//Check if we've already got it saved
				if (!this._utBots.ContainsKey(id))
				{
					//Create a new one
					UTBotOppState bot = new UTBotOppState(msg);
					bot.LastUpdated = DateTime.Now;
					this._utBots.Add(id, bot);

					//this.Dispatcher.BeginInvoke(delegate()
					//{
						UTUIBot uiBot = new UTUIBot(bot);
						cvsBots.Children.Add(uiBot);
					//});
				}
				else
				{
					//Just update it
					UTVector Location = UTVector.Parse(msg.Arguments[1]);
					UTVector Rotation = UTVector.Parse(msg.Arguments[2]);
					int Health = int.Parse(msg.Arguments[5]);
					int ArmorStrength = int.Parse(msg.Arguments[6]);
					FireType FiringType = (FireType)(int.Parse(msg.Arguments[8]));
					int CurrentAmmo = int.Parse(msg.Arguments[12]);

					//this.Dispatcher.BeginInvoke(delegate()
					//{
						if (_utBots.ContainsKey(id))
						{
							this._utBots[id].Location = Location;
							this._utBots[id].Rotation = Rotation;
							this._utBots[id].Health = Health;
							this._utBots[id].ArmorStrength = ArmorStrength;
							this._utBots[id].FiringType = FiringType;
							this._utBots[id].CurrentAmmo = CurrentAmmo;
							this._utBots[id].LastUpdated = DateTime.Now;
						}
					//});
				}
			}
		}

		internal void UpdateScores(Message msg)
		{
			//this.Dispatcher.BeginInvoke(delegate()
			//{

				foreach (string scoreInfo in msg.Arguments)
				{
					string[] info = scoreInfo.Split(Message.MESSAGE_SUBSEPARATOR);

					if (info.Length == 3)
					{
						UTIdentifier id = new UTIdentifier(info[0]);
						int score = (int)float.Parse(info[2]);

							if (this._scores.ContainsKey(id))
							{
								if (score != this._scores[id].Score)
								{
									this._scores[id].SetScore(score);

									
										this.scoreControl.Sort(this._scores);
									
								}
							}
							else if (this._utBots.ContainsKey(id))
							{
								this._scores.Add(id, new UTUIScore(id, info[1], score));
								this._scores[id].SetBot(this._utBots[id]);
								this._observableScores.Add(this._scores[id]);

								this.scoreControl.Sort(this._scores);
								
							}
						
					}
				}

				int topScore = 0;
				UTUIScore topPlayer = null;
				foreach (UTUIScore score in this._scores.Values)
				{
					if (score.Score >= topScore)
					{
						topScore = score.Score;
						topPlayer = score;
					}
				}

				if (topPlayer != null)
				{
					
						this.scoreControl.txtScoreName.Text = topPlayer.Name;
						this.scoreControl.txtScorePoints.Text = topPlayer.Score.ToString();
					
				}

			//});
		}




		private void PerformChatEvent(UTIdentifier id, string fromName, bool isTeam, string message)
		{
			
			//this.Dispatcher.BeginInvoke(delegate()
			//{
				lock (this._utBots)
				{
					if (this._utBots.ContainsKey(id))
					{
						this._utBots[id].LastChatMessage = message;
					}
				}
			//});
			
		}


        public void AddTooltip(Tooltip tooltip)
        {
            _pageRoot.Children.Add(tooltip);
        }

        public void RemoveTooltop(Tooltip tooltip)
        {
            _pageRoot.Children.Remove(tooltip);
        }

		private void RemoveOldPlayers()
		{
			lock (this._utBots)
			{
				List<UTBotOppState> oldBots = new List<UTBotOppState>();
				List<UTUIBot> oldUIBots = new List<UTUIBot>();
				List<UTUIScore> oldScores = new List<UTUIScore>();

				foreach (UTBotOppState bot in this._utBots.Values)
				{
					if (bot.LastUpdated < DateTime.Now - CHECK_INTERVAL)
					{
						oldBots.Add(bot);
						foreach (UTUIBot uiBot in cvsBots.Children)
						{
							if (uiBot.DataContext == bot)
							{
								oldUIBots.Add(uiBot);
							}
						}
						foreach (UTUIScore score in this._scores.Values)
						{
							if (score.Bot == bot)
							{
								oldScores.Add(score);
							}
						}
					}
				}

				//Remove them from our dictionary
				foreach (UTBotOppState bot in oldBots)
				{
					this._utBots.Remove(bot.Id);
				}

				//Remove them from the UI
				if (oldUIBots.Count > 0)
				{
					foreach (UTUIBot uiBot in oldUIBots)
					{
						cvsBots.Children.Remove(uiBot);
					}
				}

				if (oldScores.Count > 0)
				{
					foreach (UTUIScore score in oldScores)
					{
						this._observableScores.Remove(score);
						this._scores.Remove(score.Id);
					}

					this.scoreControl.Sort(this._scores);
				}
			}
		}


		private void imgMapImage_SizeChanged(object sender, SizeChangedEventArgs e)
		{
			if (!this._isMapInitialized && !double.IsNaN(imgMapImage.ActualWidth))
			{
				this._isMapInitialized = true;
				cvsNavPoints.Width = imgMapImage.ActualWidth;
				cvsNavPoints.Height = imgMapImage.ActualHeight;
				cvsItemPoints.Width = imgMapImage.ActualWidth;
				cvsItemPoints.Height = imgMapImage.ActualHeight;
				cvsBots.Width = imgMapImage.ActualWidth;
				cvsBots.Height = imgMapImage.ActualHeight;

				CalculateZoom();
			}
		}


		private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
		{
			CalculateZoom();
		}

		private void CalculateZoom()
		{
			if (sliderZoom != null)
			{
				double x = scrMap.HorizontalOffset / scrMap.ScrollableWidth;
				double y = scrMap.VerticalOffset / scrMap.ScrollableHeight;

				if (double.IsNaN(x) || double.IsInfinity(x)) x = 0.5;
				if (double.IsNaN(y) || double.IsInfinity(y)) y = 0.5;

				transMapScale.ScaleX = sliderZoom.Value;
				transMapScale.ScaleY = sliderZoom.Value;
				gridMap.Width = imgMapImage.ActualWidth * transMapScale.ScaleX;
				gridMap.Height = imgMapImage.ActualHeight * transMapScale.ScaleY;

				scrMap.UpdateLayout();

				scrMap.ScrollToHorizontalOffset(x * scrMap.ScrollableWidth);
				scrMap.ScrollToVerticalOffset(y * scrMap.ScrollableHeight);
			}
		}


		private void FSButton_MouseEnter(object sender, MouseEventArgs e)
		{
			FullscreenIn.Begin();
		}

		private void FSButton_MouseLeave(object sender, MouseEventArgs e)
		{
			FullscreenOut.Begin();
		}

		private void FSButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			Application.Current.Host.Content.IsFullScreen = !Application.Current.Host.Content.IsFullScreen;
		}


	}
}
